/**
 * Copyright (c) 2019-2020, JGraph Ltd
 */
/**
 * Class: mxOrgChartLayout
 *
 * Extends <mxGraphLayout> to implement organization chart layout algorithm.
 * The vertices need to be connected for this layout to work, vertices
 * with no connections are ignored.
 *
 * Example:
 *
 * (code)
 * var layout = new mxOrgChartLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 */
function mxOrgChartLayout(graph, branchOptimizer, parentChildSpacing, siblingSpacing) {
    mxGraphLayout.call(this, graph);
    this.correctY = false;

    switch (parseInt(branchOptimizer)) {
        case 0:
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_LINEAR;
            this.correctY = true;
            break;
        case 1:
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_HANGER2;
            this.correctY = true;
            break;
        case 3:
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE1;
            break;
        case 4:
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE2;
            break;
        case 5:
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_L;
            break;
        case 6:
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_R;
            break;
        case 7:
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_SMART;
            break;
        default: //and case 2
            this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_HANGER4;
            this.correctY = true;
    }

    this.parentChildSpacing = parentChildSpacing > 0 ? parentChildSpacing : 20;
    this.siblingSpacing = siblingSpacing > 0 ? siblingSpacing : 20;
};

/**
 * Extends mxGraphLayout.
 */
mxOrgChartLayout.prototype = new mxGraphLayout();
mxOrgChartLayout.prototype.constructor = mxOrgChartLayout;

//Branch Optimizers
mxOrgChartLayout.prototype.BRANCH_OPT_LINEAR = 'branchOptimizerAllLinear';
mxOrgChartLayout.prototype.BRANCH_OPT_HANGER2 = 'branchOptimizerAllHanger2';
mxOrgChartLayout.prototype.BRANCH_OPT_HANGER4 = 'branchOptimizerAllHanger4';
mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE1 = 'branchOptimizerAllFishbone1';
mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE2 = 'branchOptimizerAllFishbone2';
mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_L = 'branchOptimizerAllSingleColumnLeft';
mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_R = 'branchOptimizerAllSingleColumnRight';
mxOrgChartLayout.prototype.BRANCH_OPT_SMART = 'branchOptimizerSmart';

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>. This operates on all children of the
 * given parent.
 */
mxOrgChartLayout.prototype.execute = function (parent) {
    this.graph.model.beginUpdate();
    try {
        RPOrgChart.main(this.graph, parent, this.branchOptimizer, this.parentChildSpacing, this.siblingSpacing, this.correctY);
    } finally {
        this.graph.model.endUpdate();
    }
}

Bridge.define('RPOrgChart',
    {
        statics: {
            config: {
                init: function () {

                }
            },
            main: function (graph, parent, branchOptimizer, parentChildSpacing, siblingSpacing, correctY) {
                Bridge.Console.log = console.log;
                Bridge.Console.error = console.error;
                Bridge.Console.debug = console.debug;

                RPOrgChart.graph = graph;
                RPOrgChart.parent = parent;
                RPOrgChart.dx = 0;
                RPOrgChart.dy = 0;

                if (parent.style == 'group' && parent.geometry) {
                    RPOrgChart.dx = parent.geometry.x;
                    RPOrgChart.dy = parent.geometry.y;
                }

                RPOrgChart.branchOptimizer = branchOptimizer;
                RPOrgChart.correctY = correctY;
                RPOrgChart.parentChildSpacing = parseInt(parentChildSpacing);
                RPOrgChart.siblingSpacing = parseInt(siblingSpacing);
                RPOrgChart.buildChart(true);
            },

            diagram: {},
            dataSource: {},

            buildChart: function (initData) {
                if (initData) {
                    RPOrgChart.initDiagram();
                }
                RPOrgChart.positionBoxes();
            },

            collapseAllBoxes: function (boxContainer, isCollapsed) {
                var en = boxContainer.getBoxesById().getValues().getEnumerator();
                while (en.moveNext()) {
                    var box = en.getCurrent();
                    if (!box.IsSpecial) {
                        box.IsCollapsed = isCollapsed;
                    }
                }
            },

            generateData: function () {
                var dataSource = new OrgChart.Test.TestDataSource();

                var graph = RPOrgChart.graph;
                var cells = graph.getChildVertices(RPOrgChart.parent);

                for (var i = 0; i < cells.length; i++) {
                    var cell = cells[i];

                    if (cell.geometry != null && cell.vertex && cell.parent == RPOrgChart.parent) //Vertices and first level children only
                    {
                        // Find cell parent. If it has more than one parent, take first parent (should be an error?)
                        var parentId = null;

                        var incomingEdge = graph.getIncomingEdges(cell)[0];

                        if (incomingEdge != null && incomingEdge.source != null) {
                            parentId = incomingEdge.source.id;
                        }

                        var item = new OrgChart.Test.TestDataItem();
                        item.Id = cell.id;
                        item.ParentId = parentId;
                        dataSource.Items.add(item.getId(), item);
                    }
                }

                return dataSource;
            },

            initDiagram: function () {
                var dataSource = RPOrgChart.generateData();

                RPOrgChart.dataSource = dataSource;

                var boxContainer = new OrgChart.Layout.BoxContainer.$ctor1(dataSource);
                RPOrgChart.diagram = new OrgChart.Layout.Diagram();

                var diagram = RPOrgChart.diagram;
                diagram.setBoxes(boxContainer);

                var linearLayoutStrategy = new OrgChart.Layout.LinearLayoutStrategy();
                linearLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
                linearLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                linearLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("linear", linearLayoutStrategy);

                var multiLineHangerLayoutStrategy = new OrgChart.Layout.MultiLineHangerLayoutStrategy();
                multiLineHangerLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
                multiLineHangerLayoutStrategy.MaxSiblingsPerRow = 2;
                multiLineHangerLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                multiLineHangerLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("hanger2", multiLineHangerLayoutStrategy);

                multiLineHangerLayoutStrategy = new OrgChart.Layout.MultiLineHangerLayoutStrategy();
                multiLineHangerLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
                multiLineHangerLayoutStrategy.MaxSiblingsPerRow = 4;
                multiLineHangerLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                multiLineHangerLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("hanger4", multiLineHangerLayoutStrategy);

                var singleColumnLayoutStrategy = new OrgChart.Layout.SingleColumnLayoutStrategy();
                singleColumnLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Right;
                singleColumnLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                singleColumnLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("singleColumnRight", singleColumnLayoutStrategy);

                singleColumnLayoutStrategy = new OrgChart.Layout.SingleColumnLayoutStrategy();
                singleColumnLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Left;
                singleColumnLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                singleColumnLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("singleColumnLeft", singleColumnLayoutStrategy);

                var fishboneLayoutStrategy = new OrgChart.Layout.MultiLineFishboneLayoutStrategy();
                fishboneLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
                fishboneLayoutStrategy.MaxGroups = 1;
                fishboneLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                fishboneLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("fishbone1", fishboneLayoutStrategy);

                fishboneLayoutStrategy = new OrgChart.Layout.MultiLineFishboneLayoutStrategy();
                fishboneLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
                fishboneLayoutStrategy.MaxGroups = 2;
                fishboneLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                fishboneLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("fishbone2", fishboneLayoutStrategy);

                var hstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
                hstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
                hstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleRowHorizontal;
                hstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                hstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("hstack", hstackLayoutStrategy);

                var vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
                vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
                vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
                vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("vstack", vstackLayoutStrategy);

                vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
                vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
                vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
                vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("vstackMiddle", vstackLayoutStrategy);

                vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
                vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
                vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
                vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("vstackTop", vstackLayoutStrategy);

                var assistantsLayoutStrategy = new OrgChart.Layout.FishboneAssistantsLayoutStrategy();
                assistantsLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
                assistantsLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
                assistantsLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
                diagram.LayoutSettings.LayoutStrategies.add("assistants", assistantsLayoutStrategy);

                diagram.LayoutSettings.DefaultLayoutStrategyId = "vstack";
                diagram.LayoutSettings.DefaultAssistantLayoutStrategyId = "assistants";
                //diagram.LayoutSettings.setBranchSpacing(5);
            },

            getBoxLevel: function (boxContainer, box) {
                var level = 0;
                var obj = {};
                while (box.ParentId > 0) {
                    if (!boxContainer.getBoxesById().tryGetValue(box.ParentId, obj)) {
                        break;
                    }
                    box = obj.v;
                    level++;
                }

                return level;
            },

            onLayoutStateChanged: function (sender, args) {
                if (args.State.getCurrentOperation() === OrgChart.Layout.LayoutState.Operation.PreprocessVisualTree) {
                    // When layout algorithm is ready to preprocess the tree,
                    // we need to have box sizes ready -> hence have to render visible boxes in HTML.
                    // Rendering can happen at earlier time, but it's just more convenient to do it here,
                    // to utilize some readily available information about visual tree.
                    RPOrgChart.renderBoxes();
                }
            },

            renderBoxes: function () {
                var visitorFunc = function (node) {
                    var box = node.Element;

                    if (box.getIsDataBound()) {
                        // we're being run when nodes have already been marked as visible or hidden,
                        // based on IsCollapsed attribute of each Box
                        // so use this knowledge to prevent unnecessary rendering of invisible branches
                        if (node.State.IsHidden) {
                            return true;
                        }

                        box.Size = RPOrgChart.getBoxElementSize(box.DataId);
                    }

                    return true;
                }

                RPOrgChart.diagram.getVisualTree().IterateParentFirst(visitorFunc);
            },

            getBranchOptimizerFunc: function () {
                return RPOrgChart[RPOrgChart.branchOptimizer];
            },

            branchOptimizerAllLinear: function (node) {
                return node.getIsAssistantRoot() ? null : "linear";
            },

            branchOptimizerAllHanger2: function (node) {
                return node.getIsAssistantRoot() ? null : "hanger2";
            },

            branchOptimizerAllHanger4: function (node) {
                return node.getIsAssistantRoot() ? null : "hanger4";
            },

            branchOptimizerAllFishbone1: function (node) {
                return node.getIsAssistantRoot() ? null : "fishbone1";
            },

            branchOptimizerAllFishbone2: function (node) {
                return node.getIsAssistantRoot() ? null : "fishbone2";
            },

            branchOptimizerAllSingleColumnLeft: function (node) {
                return node.getIsAssistantRoot() ? null : "singleColumnRight";
            },

            branchOptimizerAllSingleColumnRight: function (node) {
                return node.getIsAssistantRoot() ? null : "singleColumnLeft";
            },

            branchOptimizerStackers: function (node) {
                if (node.getIsAssistantRoot()) {
                    return null;
                }
                return node.Level === 0 // this is Node for boxContainer.SystemRoot, which is not visible itself
                    ? "vstackTop"
                    : node.Level === 1 // this is children of SystemRoot - they appear as roots in the diagram
                        ? "vstackMiddle"
                        : "hstack";

            },

            branchOptimizerSmart: function (node) {
                if (node.getIsAssistantRoot()) {
                    return null;
                }

                var childCount = node.getChildCount();

                if (childCount <= 1) {
                    return "vstack";
                }

                var nonLeafChildren = 0;
                for (var i = 0; i < childCount; i++) {
                    if (node.Children.getItem(i).getChildCount() > 0) {
                        nonLeafChildren++;
                    }
                }

                if (nonLeafChildren <= 1) {
                    if (childCount <= 4) {
                        return "vstack";
                    }
                    if (childCount <= 8) {
                        return "fishbone1";
                    }
                    return "fishbone2";
                }

                return "hanger4";
            },

            boxSizeFunc: function (dataId) {
                // ChartLayoutAlgorithm requires this function to accept data ID
                // so have to convert it to Box ID first, to get rendered visual element
                var boxId = RPOrgChart.diagram.getBoxes().getBoxesByDataId().getItem(dataId).Id;
                return RPOrgChart.diagram.getBoxes().getBoxesById().getItem(boxId).Size;
            },

            getBoxElementSize: function (boxId) {
                var geo = RPOrgChart.graph.model.cells[boxId].geometry;
                return new OrgChart.Layout.Size.$ctor1(geo.width, geo.height);
            },

            positionBoxes: function () {
                var diagram = RPOrgChart.diagram;

                var state = new OrgChart.Layout.LayoutState(diagram);

                state.addOperationChanged(RPOrgChart.onLayoutStateChanged);
                state.BoxSizeFunc = Bridge.fn.bind(this, RPOrgChart.boxSizeFunc, null, true);
                state.LayoutOptimizerFunc = Bridge.fn.bind(this, RPOrgChart.getBranchOptimizerFunc(), null, true);

                OrgChart.Layout.LayoutAlgorithm.Apply(state);

                var diagramBoundary = OrgChart.Layout.LayoutAlgorithm.ComputeBranchVisualBoundingRect(diagram.getVisualTree());

                var offsetx = -diagramBoundary.getLeft() + diagramBoundary.getTop();

                var graph = RPOrgChart.graph;
                var cells = graph.model.cells;
                var pointsList = [];

                var visitorVertexFunc = function (node) {
                    if (node.State.IsHidden) {
                        return false;
                    }

                    var box = node.Element;

                    if (box.getIsDataBound()) {
                        var cell = cells[box.DataId];
                        var geo = cell.geometry.clone();
                        geo.x = node.State.TopLeft.X + offsetx;
                        geo.y = node.State.TopLeft.Y;
                        graph.model.setGeometry(cell, geo);
                    }

                    return true;
                }

                var visitorEdgeFunc = function (node) {
                    //The algorithm default is 5 px only above the node, this centers it
                    var yCorrection = RPOrgChart.correctY ? Math.min(0, -(RPOrgChart.parentChildSpacing / 2) + 5) : 0;
                    // Render connectors
                    if (node.State.Connector != null) {

                        var cell = cells[node.Element.DataId];

                        var outgoingEdge = graph.getOutgoingEdges(cell);

                        var uniquePoints = {};

                        //Sort segments points from top to bottom or left to right + add offset
                        for (var ix = 0; ix < node.State.Connector.Segments.length; ix++) {
                            var edge = node.State.Connector.Segments[ix];
                            edge.mark = 1 << ix; //TODO Support up to 31 segments. In this a limit?
                            edge.From.X += offsetx;
                            edge.To.X += offsetx;
                            var fx = edge.From.X, fy = edge.From.Y, tx = edge.To.X, ty = edge.To.Y;

                            if ((fx == tx && fy > ty) || (fy == ty && fx > tx)) {
                                var tmp = edge.From;
                                edge.From = edge.To;
                                edge.To = tmp;
                            }
                        }

                        //Collecting points including intersection of segments
                        for (var ix = 0; ix < node.State.Connector.Segments.length; ix++) {
                            var edge = node.State.Connector.Segments[ix];
                            var fx = edge.From.X, fy = edge.From.Y, tx = edge.To.X, ty = edge.To.Y;
                            var fp = new mxPoint(fx, fy);
                            pointsList.push(fp);
                            fp.mark = edge.mark;
                            var up = uniquePoints[fx + ',' + fy];

                            if (up != null) {
                                up.mark |= fp.mark;
                            } else {
                                uniquePoints[fx + ',' + fy] = fp;
                            }

                            var tp = new mxPoint(tx, ty);
                            pointsList.push(tp);
                            tp.mark = edge.mark;
                            var up = uniquePoints[tx + ',' + ty];

                            if (up != null) {
                                up.mark |= tp.mark;
                            } else {
                                uniquePoints[tx + ',' + ty] = tp;
                            }

                            //Find intersections
                            for (var j = ix + 1; j < node.State.Connector.Segments.length; j++) {
                                var e2 = node.State.Connector.Segments[j];
                                var fx2 = e2.From.X, fy2 = e2.From.Y, tx2 = e2.To.X, ty2 = e2.To.Y;

                                if (fx == tx && fy <= fy2 && ty >= fy2 && fx2 <= fx && tx2 >= fx) //Ver |_ Hor
                                {
                                    var ip = new mxPoint(fx, fy2);
                                    pointsList.push(ip);
                                    ip.mark = edge.mark | e2.mark;
                                    var up = uniquePoints[fx + ',' + fy2];

                                    if (up != null) {
                                        up.mark |= ip.mark;
                                    } else {
                                        uniquePoints[fx + ',' + fy2] = ip;
                                    }
                                } else if (fy == ty && fx <= fx2 && tx >= fx2 && fy2 <= fy && ty2 >= fy) //Hor _| Ver
                                {
                                    var ip = new mxPoint(fx2, fy);
                                    pointsList.push(ip);
                                    ip.mark = edge.mark | e2.mark;
                                    var up = uniquePoints[fx2 + ',' + fy]

                                    if (up != null) {
                                        up.mark |= ip.mark;
                                    } else {
                                        uniquePoints[fx2 + ',' + fy] = ip;
                                    }
                                }
                            }
                        }

                        //Sort points on y then x
                        var pointsArr = [];

                        for (var k in uniquePoints) {
                            pointsArr.push(uniquePoints[k]);
                        }

                        pointsArr.sort(function (a, b) {
                            var dy = a.y - b.y;

                            return dy == 0 ? a.x - b.x : dy;
                        });

                        function pointOnCell(geo, p) {
                            return p.x >= geo.x && p.x <= geo.x + geo.width && p.y >= geo.y && p.y <= geo.y + geo.height;
                        };

                        function adjustEdgeGeoAndStyle(edge, edgePoints) {
                            var eGeo = edge.geometry.clone();

                            for (var i = 0; edgePoints && i < edgePoints.length; i++) {
                                if (!edgePoints[i].corrected) {
                                    edgePoints[i].y += yCorrection;
                                    edgePoints[i].corrected = true
                                }
                            }

                            eGeo.points = edgePoints;
                            graph.model.setGeometry(edge, eGeo);

                            //Remove entry and exit points
                            graph.setCellStyles('entryX', null, [edge]);
                            graph.setCellStyles('entryY', null, [edge]);
                            graph.setCellStyles('exitX', null, [edge]);
                            graph.setCellStyles('exitY', null, [edge]);
                            //Set type orthogonal
                            graph.setCellStyles('edgeStyle', 'orthogonalEdgeStyle', [edge]);
                        };

                        var outgoingEdge = graph.getOutgoingEdges(cell);

                        //Simple case of a single segment. TODO Handle this case earlier
                        if (pointsArr.length == 2 && outgoingEdge.length == 1) {
                            adjustEdgeGeoAndStyle(outgoingEdge[0], pointsArr);
                        } else {
                            var srcGeo = cell.geometry;
                            var srcP;

                            //Find src starting point //TODO It should be first point always?
                            for (var i = 0; i < pointsArr.length; i++) {
                                if (pointOnCell(srcGeo, pointsArr[i])) {
                                    srcP = pointsArr[i];
                                    break;
                                }
                            }

                            var selected;

                            function getNextPoint(lp) {
                                for (var i = 0; i < pointsArr.length; i++) {
                                    var p = pointsArr[i];
                                    if (selected[p.x + ',' + p.y]) continue;

                                    if (p.mark & lp.mark) {
                                        selected[p.x + ',' + p.y] = true;
                                        return p;
                                    }
                                }
                            }

                            for (var j = 0; j < outgoingEdge.length; j++) {
                                if (outgoingEdge[j].target != null) {
                                    selected = {};
                                    selected[srcP.x + ',' + srcP.y] = true;
                                    var trgGeo = outgoingEdge[j].target.geometry;

                                    var edgePoints = [srcP];
                                    var lp = srcP;
                                    var safeGuard = 0;
                                    //Is BFS better?
                                    while (safeGuard < 1000) {
                                        safeGuard++;
                                        var np = getNextPoint(lp);

                                        //retract, then remove this point
                                        if (np == null) {
                                            edgePoints.pop();
                                            lp = edgePoints[edgePoints.length - 1];
                                        } else {
                                            edgePoints.push(np);
                                            lp = np;
                                            if (pointOnCell(trgGeo, np)) break;
                                        }
                                    }

                                    //Remove retracted points TODO can we do it in a better way?
                                    if (edgePoints.length > 2) {
                                        var spX = edgePoints[0].x;
                                        var lpX = edgePoints[edgePoints.length - 1].x;

                                        for (var i = edgePoints.length - 2; i > 0; i--) {
                                            if ((spX > lpX && edgePoints[i].x < lpX) || (spX < lpX && edgePoints[i].x < spX)) {
                                                edgePoints.splice(i, 1);
                                            }
                                        }
                                    }

                                    var eGeo = outgoingEdge[j].geometry.clone();
                                    eGeo.points = edgePoints;
                                    RPOrgChart.graph.model.setGeometry(outgoingEdge[j], eGeo);

                                    //Fix edge points and style
                                    adjustEdgeGeoAndStyle(outgoingEdge[j], edgePoints);
                                }
                            }
                        }
                    }

                    return true;
                }

                diagram.getVisualTree().IterateParentFirst(visitorVertexFunc);
                diagram.getVisualTree().IterateParentFirst(visitorEdgeFunc);

                //Cleanup
                for (var i = 0; i < pointsList.length; i++) {
                    delete pointsList[i].mark;
                    delete pointsList[i].corrected;
                }
            }

        }
    });

Bridge.init();
